package pkg

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
	agentV3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
	"time"
)

type Redis struct {
	client *redis.Client
	host   string
	port   int
	prefix string
}

var RedisCli = &Redis{}

func NewRedisClient(host string, port int, auth string, poolSize int, defaultDB int, prefix string) *Redis {
	client := redis.NewClient(&redis.Options{
		Addr:     fmt.Sprintf("%s:%d", host, port),
		Password: auth,
		DB:       defaultDB,
		PoolSize: poolSize,
	})
	return &Redis{
		client: client,
		host:   host,
		port:   port,
		prefix: prefix,
	}
}

func (p *Redis) Init(host string, port int, auth string, poolSize int, defaultDB int, prefix string) {
	RedisCli = NewRedisClient(host, port, auth, poolSize, defaultDB, prefix)
}

func (p *Redis) Client() *redis.Client {
	return p.client
}

func (p *Redis) Get(c context.Context, key string) *redis.StringCmd {
	span, err := Tracer.CreateExitSpan(c, ExitSpanOptions{
		OperationName: "redis get",
		Component:     ComponentRedisServer,
		Peer:          fmt.Sprintf("%s:%d", RedisCli.host, RedisCli.port),
		Layer:         agentV3.SpanLayer_Cache,
	})
	if err == nil {
		defer span.End()
	}
	key = p.prefix + key
	stringCmd := p.client.Get(c, key)
	_, err = stringCmd.Result()
	if err != nil {
		Tracer.SpanErr(span, err.Error())
	}
	// span.Log(time.Now(), "content of redis get"+stringCmd.String())
	return stringCmd
}

func (p *Redis) Set(c context.Context, key string, value interface{}, expiration time.Duration) error {
	span, err := Tracer.CreateExitSpan(c, ExitSpanOptions{
		OperationName: "redis set",
		Component:     ComponentRedisServer,
		Peer:          fmt.Sprintf("%s:%d", RedisCli.host, RedisCli.port),
		Layer:         agentV3.SpanLayer_Cache,
	})
	if err == nil {
		defer span.End()
	}
	key = p.prefix + key
	err = p.client.Set(c, key, value, expiration).Err()
	if err != nil {
		Tracer.SpanErr(span, err.Error())
		return fmt.Errorf("failed to set redis key: %v", err)
	}
	return nil
}

func (p *Redis) SetNX(c context.Context, key string, value interface{}, expiration time.Duration) error {
	span, err := Tracer.CreateExitSpan(c, ExitSpanOptions{
		OperationName: "redis SetNX",
		Component:     ComponentRedisServer,
		Peer:          fmt.Sprintf("%s:%d", RedisCli.host, RedisCli.port),
		Layer:         agentV3.SpanLayer_Cache,
	})
	if err == nil {
		defer span.End()
	}
	key = p.prefix + key
	result := p.client.SetNX(c, key, value, expiration)
	ok, err := result.Result()
	if err != nil {
		// 其他错误，如连接错误等
		err = fmt.Errorf("failed to set key in SetNX: %v", err)
	} else if !ok {
		// 锁已存在
		err = fmt.Errorf("key already exist in SetNX")
	}
	if err != nil {
		Tracer.SpanErr(span, err.Error())
	}
	return err
}

func (p *Redis) Del(c context.Context, key string) error {
	span, err := Tracer.CreateExitSpan(c, ExitSpanOptions{
		OperationName: "redis Del",
		Component:     ComponentRedisServer,
		Peer:          fmt.Sprintf("%s:%d", RedisCli.host, RedisCli.port),
		Layer:         agentV3.SpanLayer_Cache,
	})
	if err == nil {
		defer span.End()
	}
	key = p.prefix + key
	err = p.client.Del(c, key).Err()
	if err != nil {
		Tracer.SpanErr(span, err.Error())
		return fmt.Errorf("failed to delete redis key: %v", err)
	}
	return nil
}
